package com.literature.search.file.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.easyes.core.conditions.select.LambdaEsQueryWrapper;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.literature.search.core.common.EsIndexConstant;
import com.literature.search.core.common.SortConstant;
import com.literature.search.core.entity.EsFileObject;
import com.literature.search.core.entity.EsPageObject;
import com.literature.search.core.entity.EsPicObject;
import com.literature.search.core.entity.FileDocument;
import com.literature.search.core.entity.R.AjaxJson;
import com.literature.search.core.entity.esindex.HistoryKeywords;
import com.literature.search.core.mapper.EsFileObjectMapper;
import com.literature.search.core.mapper.HistoryKeywordsMapper;
import com.literature.search.file.entity.dto.SearchAllDTO;
import com.literature.search.file.entity.dto.SearchSingleDTO;
import com.literature.search.file.entity.dto.TopNDTO;
import com.literature.search.file.entity.vo.*;
import com.literature.search.file.service.IElasticService;
import com.literature.search.file.service.RedisService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.client.indices.AnalyzeResponse;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.InnerHitBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @Author: DangKong
 * @Date: 2023/3/20 23:43
 * @Description:
 */

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ElasticServiceImpl implements IElasticService {


    private final EsFileObjectMapper esFileObjectMapper;

    private final HistoryKeywordsMapper historyKeywordsMapper;

    private final String PAGE_CONTENTS_CONTENT = "pageContentsContent"; //为了前端展示，将pageContents.content改为pageContentsContent

    private final String PIC_CONTENTS_CONTENT = "picContentsContent"; //为了前端展示，将picContents.content改为picContentsContent


    private final RestHighLevelClient client;

    @Resource
    private RedisService redisService;

    @Override
    public AjaxJson searchAllByKeyword(SearchAllDTO searchAllDTO) throws IOException {

        //需求:
        // 1、根据关键字查询所有文档,查询文档的所有字段（包括父级和嵌套类型）
        // 2、同时使用内部查询innerHit查询待查询字段keyword在每个PageContent的相关性得分.
        SearchRequest searchRequest = new SearchRequest();

        searchRequest.indices(EsIndexConstant.CN_INDEX_NAME, EsIndexConstant.EN_INDEX_NAME);

        String keyword = searchAllDTO.getKeyword();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //es多个query时，query条件会被覆盖掉，只会保存最后一个query，为了解决这个问题，可以使用boolQuery

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (searchAllDTO.getIsFromText() != null && searchAllDTO.getIsFromText()) {
            boolQuery.should(QueryBuilders.nestedQuery(
                    EsIndexConstant.PAGE_CONTENTS,
                    QueryBuilders.boolQuery().must(QueryBuilders.matchQuery(EsIndexConstant.PAGE_CONTENTS_CONTENT, keyword)),
                    ScoreMode.Avg
            ).innerHit(new InnerHitBuilder().setSize(3).setSorts(CollUtil.newArrayList(SortBuilders.scoreSort().order(SortOrder.DESC)))));
        }

        if (searchAllDTO.getIsFromImage() != null && searchAllDTO.getIsFromImage()) {
            boolQuery.should(QueryBuilders.nestedQuery(
                    EsIndexConstant.PIC_CONTENTS,
                    QueryBuilders.boolQuery().must(QueryBuilders.matchQuery(EsIndexConstant.PIC_CONTENTS_CONTENT, keyword)),
                    ScoreMode.Avg
            ).innerHit(new InnerHitBuilder().setSize(1).setSorts(CollUtil.newArrayList(SortBuilders.scoreSort().order(SortOrder.DESC)))));
        }

        //除了上面的两个嵌套查询，还有其他的查询条件，比如标题fileName、上传人uploadUser
        boolQuery.should(QueryBuilders.matchQuery(EsIndexConstant.FILE_NAME, keyword));
        boolQuery.should(QueryBuilders.matchQuery(EsIndexConstant.UPLOAD_USER, keyword));

        searchSourceBuilder.query(boolQuery);

        // 设置分页
        searchSourceBuilder.from((searchAllDTO.getPageNum() - 1) * searchAllDTO.getPageSize());
        searchSourceBuilder.size(searchAllDTO.getPageSize());

        //设置排序方式
        if (searchAllDTO.getSort().equals(SortConstant.SCORE)){
            searchSourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC)); //按照相关性得分排序
        }else {
            searchSourceBuilder.sort(SortBuilders.fieldSort(searchAllDTO.getSort()).order(SortOrder.DESC)); //按照相关性得分排序
        }

        // 设置Highlighting 高亮  关键字上下文100个字符
        searchSourceBuilder.highlighter(new HighlightBuilder()
                .field("*") //设置高亮字段
                .requireFieldMatch(false)
                .preTags("<span style='color:red'>")
                .postTags("</span>"));


        //返回的字段中排出父级元素的嵌套类型的字段，因为内部命中查询已经返回了
        searchSourceBuilder.fetchSource(null, new String[]{EsIndexConstant.PAGE_CONTENTS,EsIndexConstant.PIC_CONTENTS}); //参数1：包含的字段，参数2：排除的字段


        searchRequest.source(searchSourceBuilder);
        SearchResponse response = esFileObjectMapper.search(searchRequest, RequestOptions.DEFAULT);

        //如果结果不为空
        if (response.getHits().getHits().length == 0) {
            return AjaxJson.getSuccessData(null);
        }
        SearchAllVO searchVO = new SearchAllVO();
        searchVO.setTotal((int) response.getHits().getTotalHits().value); //总条数
        searchVO.setMaxScore(response.getHits().getMaxScore()); //最大相关性得分

        List<EsFileObjectVO> esFileObjectListVO = new ArrayList<>(response.getHits().getHits().length);
        //遍历response.getHits().getHits()，将结果封装到EsFileObject中
        Arrays.stream(response.getHits().getHits()).forEach(hit -> {
            EsFileObjectVO esFileObjectVO = new EsFileObjectVO();

            esFileObjectVO.setId(hit.getId()); //文档id
            esFileObjectVO.setScore(hit.getScore()); //相关性得分

            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            esFileObjectVO.setFileName(sourceAsMap.get(EsIndexConstant.FILE_NAME).toString()); //文件名
            esFileObjectVO.setThumbId(sourceAsMap.get(EsIndexConstant.THUMB_ID).toString()); //缩略图id
            esFileObjectVO.setUploadDate(sourceAsMap.get(EsIndexConstant.UPLOAD_DATE).toString()); //上传时间
            esFileObjectVO.setUploadUser(sourceAsMap.get(EsIndexConstant.UPLOAD_USER).toString()); //上传人
            esFileObjectVO.setLike((Integer) sourceAsMap.get(EsIndexConstant.LIKE)); //点赞数
            esFileObjectVO.setCollect((Integer) sourceAsMap.get(EsIndexConstant.COLLECT)); //收藏数
            esFileObjectVO.setDownload((Integer) sourceAsMap.get(EsIndexConstant.DOWNLOAD)); //下载数
            esFileObjectVO.setView((Integer) sourceAsMap.get(EsIndexConstant.VIEW)); //浏览数
            esFileObjectVO.setPicContents(new ArrayList<>());
            esFileObjectVO.setPageContents(new ArrayList<>());
            // 高亮字段
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            Map<String,String> highlightContentMap = new HashMap<>();
            if (highlightFields != null && highlightFields.size() > 0) {
                if (highlightFields.get(EsIndexConstant.FILE_NAME) != null) {
                    highlightContentMap.put(EsIndexConstant.FILE_NAME, highlightFields.get(EsIndexConstant.FILE_NAME).fragments()[0].toString());
                }
                if(highlightFields.get(EsIndexConstant.UPLOAD_USER) != null){
                    highlightContentMap.put(EsIndexConstant.UPLOAD_USER, highlightFields.get(EsIndexConstant.UPLOAD_USER).fragments()[0].toString());
                }
                if(highlightFields.get(EsIndexConstant.PAGE_CONTENTS_CONTENT) != null){
                    HighlightField field = highlightFields.get(EsIndexConstant.PAGE_CONTENTS_CONTENT);
                    Text[] fragments = field.getFragments();
                    StringBuilder sb = new StringBuilder();
                    for (Text text : fragments) {
                        sb.append(text);
                    }
                    highlightContentMap.put(PAGE_CONTENTS_CONTENT, sb.toString()); //高亮内容
                }
                if(highlightFields.get(EsIndexConstant.PIC_CONTENTS_CONTENT) != null){
                    HighlightField field = highlightFields.get(EsIndexConstant.PIC_CONTENTS_CONTENT);
                    Text[] fragments = field.getFragments();
                    StringBuilder sb = new StringBuilder();
                    for (Text text : fragments) {
                        sb.append(text);
                    }
                    highlightContentMap.put(PIC_CONTENTS_CONTENT, sb.toString()); //高亮内容
                }
            }
            esFileObjectVO.setHighlightContentMap(highlightContentMap);

            esFileObjectListVO.add(esFileObjectVO);
        });
        return AjaxJson.getSuccessData(esFileObjectListVO);
    }

    @Override
    public AjaxJson getIndexInfo() {
        GetAliasesRequest request = new GetAliasesRequest();
        GetAliasesResponse response = null;
        try {
            response = client.indices().getAlias(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Map<String, Set<AliasMetadata>> map = null;
        if (response != null) {
            map = response.getAliases();
        }
        return AjaxJson.getSuccessData(map);
    }

    @Override
    public List<RecommendVO> topN(TopNDTO topNDTO) {
        // 搜索两个索引中xx最多的前n个 按照view降序排序，不评分 不返回pageContents、picContents
        String type = topNDTO.getType();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(topNDTO.getTopN());
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        searchSourceBuilder.sort(type, SortOrder.DESC);
        searchSourceBuilder.fetchSource(null, new String[]{EsIndexConstant.PAGE_CONTENTS,EsIndexConstant.PIC_CONTENTS,EsIndexConstant.IS_ENGLISH});
        SearchRequest searchRequest = new SearchRequest(EsIndexConstant.CN_INDEX_NAME,EsIndexConstant.EN_INDEX_NAME);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<RecommendVO> recommendVOList = new ArrayList<>();
        if (searchResponse != null) {
            Arrays.stream(searchResponse.getHits().getHits()).forEach(hit -> {
                RecommendVO recommendVO = new RecommendVO();
                recommendVO.setFileId(hit.getId());
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                recommendVO.setFileName(sourceAsMap.get(EsIndexConstant.FILE_NAME).toString());
                recommendVO.setThumbnailId(hit.getSourceAsMap().get(EsIndexConstant.THUMB_ID).toString());
                recommendVO.setUploadUser(hit.getSourceAsMap().get(EsIndexConstant.UPLOAD_USER).toString());
                recommendVO.setUploadDate(hit.getSourceAsMap().get(EsIndexConstant.UPLOAD_DATE).toString());
                recommendVO.setCollect(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.COLLECT).toString()));
                recommendVO.setView(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.VIEW).toString()));
                recommendVO.setDownload(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.DOWNLOAD).toString()));
                recommendVO.setLike(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.LIKE).toString()));
                recommendVOList.add(recommendVO);
            });
        }
        return recommendVOList;
    }

    @Override
    public List<RecommendVO> recommendByRecentlyViewed() {
        String userId = StpUtil.getLoginIdAsString();
        // 1、从历史关键词中提取出现最多的前N个
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        searchSourceBuilder.query(QueryBuilders.boolQuery().filter(QueryBuilders.termQuery(EsIndexConstant.USER_ID, userId)));
        searchSourceBuilder.aggregation(AggregationBuilders.terms(EsIndexConstant.TOPN).field(EsIndexConstant.KEYWORD).size(10));
        SearchRequest searchRequest = new SearchRequest(EsIndexConstant.HISTORY_KEYWORDS_INDEX_NAME);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 获取聚合结果
        Terms terms = null;
        if (searchResponse != null) {
            terms = searchResponse.getAggregations().get(EsIndexConstant.TOPN);
        }

        //用提取出来的10个关键词查询
        List<RecommendVO> recommendVOList = new ArrayList<>();
        //将所有关键字拼接成一个字符串
        StringBuilder sb = new StringBuilder();
        if (terms != null) {
            for (Terms.Bucket bucket : terms.getBuckets()) {
                sb.append(bucket.getKeyAsString()).append(" ");
            }
        }
        // 2、根据关键词查询 取得分最高的前5个作为推荐  不返回pageContents、picContents
        SearchSourceBuilder searchSourceBuilder1 = new SearchSourceBuilder();
        searchSourceBuilder1.size(5);
        searchSourceBuilder1.query(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery(EsIndexConstant.FILE_NAME, sb.toString())));
        searchSourceBuilder1.fetchSource(null, new String[]{EsIndexConstant.PAGE_CONTENTS_CONTENT, EsIndexConstant.PIC_CONTENTS_CONTENT,EsIndexConstant.IS_ENGLISH});
        SearchRequest searchRequest1 = new SearchRequest(EsIndexConstant.CN_INDEX_NAME, EsIndexConstant.EN_INDEX_NAME);
        searchRequest1.source(searchSourceBuilder1);
        SearchResponse searchResponse1 = null;
        try {
            searchResponse1 = client.search(searchRequest1, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        SearchHits hits = null;
        if (searchResponse1 != null) {
            hits = searchResponse1.getHits();
        }
        if (hits != null) {
            for (SearchHit hit : hits) {
                RecommendVO recommendVO = new RecommendVO();
                recommendVO.setFileId(hit.getId());
                recommendVO.setFileName(hit.getSourceAsMap().get(EsIndexConstant.FILE_NAME).toString());
                recommendVO.setThumbnailId(hit.getSourceAsMap().get(EsIndexConstant.THUMB_ID).toString());
                recommendVO.setUploadUser(hit.getSourceAsMap().get(EsIndexConstant.UPLOAD_USER).toString());
                recommendVO.setUploadDate(hit.getSourceAsMap().get(EsIndexConstant.UPLOAD_DATE).toString());
                recommendVO.setCollect(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.COLLECT).toString()));
                recommendVO.setView(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.VIEW).toString()));
                recommendVO.setDownload(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.DOWNLOAD).toString()));
                recommendVO.setLike(Integer.parseInt(hit.getSourceAsMap().get(EsIndexConstant.LIKE).toString()));
                recommendVOList.add(recommendVO);
            }
        }
       return recommendVOList;

    }
    @Override
    public List<HistoryKeywords> getHistoryKeywords() {
        String userId = StpUtil.getLoginIdAsString();
        List<HistoryKeywords> historyKeywordsList = historyKeywordsMapper.selectList(new LambdaEsQueryWrapper<>(HistoryKeywords.class)
                .eq(HistoryKeywords::getUserId, userId));
        return historyKeywordsList;
    }

    @Override
    public int insertHistoryKeywords(String userId, String fileName, Integer originHistoryKeywordsCount) {
        //判断fileName是否为空
        if (StringUtils.isEmpty(fileName)) {
            return 0;
        }
        // 分词
        List<AnalyzeResponse.AnalyzeToken> tokens = analyze(fileName);
        // 将历史关键词插入到es中 ,写入ES记录的ID编号为:[用户ID]_[originHistoryKeywordsCount]
        List<HistoryKeywords> historyKeywordsList = new ArrayList<>();
        for (AnalyzeResponse.AnalyzeToken token : tokens) {
            HistoryKeywords historyKeywords = new HistoryKeywords();
            StringBuilder sb = new StringBuilder();
            sb.append(userId).append("_").append(originHistoryKeywordsCount++);
            historyKeywords.setId(sb.toString());
            historyKeywords.setUserId(userId);
            historyKeywords.setKeyword(token.getTerm());
            historyKeywordsList.add(historyKeywords);
        }
        historyKeywordsMapper.insertBatch(historyKeywordsList);
        return tokens.size();
    }

    @Override
    public List<AnalyzeResponse.AnalyzeToken> analyze(String text) {
        AnalyzeRequest request = AnalyzeRequest.withGlobalAnalyzer( "ik_max_word", text);
        AnalyzeResponse response = null;
        try {
            response = client.indices().analyze(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<AnalyzeResponse.AnalyzeToken> tokens = null;
        if (response != null) {
            tokens = response.getTokens();
        }
        return tokens ;

    }

    @Override
    public Boolean deleteIndex() {
        return esFileObjectMapper.deleteIndex(EsIndexConstant.EN_INDEX_NAME, EsIndexConstant.CN_INDEX_NAME);
    }

    @Override
    public int update(List<FileDocument> fileDocuments) {
        // 只更新es中对应文件的点赞、收藏、浏览、下载
        ArrayList<EsFileObject> esFileObjectList = new ArrayList<>();
        AtomicBoolean isContainEnglishFile = new AtomicBoolean(false); // 0表示不包含英文文件，1表示包含英文文件
        AtomicBoolean isContainChineseFile = new AtomicBoolean(false); // 0表示不包含中文文件，1表示包含中文文件
        fileDocuments.parallelStream().forEach(fileDocument -> {
            EsFileObject esFileObject = new EsFileObject();
            esFileObject.setId(fileDocument.getId());
            esFileObject.setLike(fileDocument.getLike());
            esFileObject.setCollect(fileDocument.getCollect());
            esFileObject.setDownload(fileDocument.getDownload());
            esFileObject.setView(fileDocument.getView());
            esFileObjectList.add(esFileObject);
            if (fileDocument.getIsEnglish() != null && fileDocument.getIsEnglish()) {
                isContainEnglishFile.set(true);
            }else {
                isContainChineseFile.set(true);
            }
        });
        StringBuilder sb = new StringBuilder();
        if (isContainEnglishFile.get()) {
            sb.append(EsIndexConstant.EN_INDEX_NAME).append(",");
        }
        if (isContainChineseFile.get()) {
            sb.append(EsIndexConstant.CN_INDEX_NAME).append(",");
        }
        return esFileObjectMapper.updateBatchByIds(esFileObjectList, sb.substring(0, sb.length() - 1));
    }

    @Override
    //TODO 优化 排除不需要的字段
    public AjaxJson searchSingle(SearchSingleDTO searchSingleDTO) throws IOException {

        Integer pageSize = searchSingleDTO.getPageSize();
        Integer pageNum = searchSingleDTO.getPageNum();
        String fileId = searchSingleDTO.getId();

        SearchRequest searchRequest = new SearchRequest(EsIndexConstant.CN_INDEX_NAME, EsIndexConstant.EN_INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //在两个索引中查询指定id的文档
        boolQueryBuilder.must(QueryBuilders.termQuery("id", fileId));

        //文档名称
        boolQueryBuilder.should(QueryBuilders.matchQuery(EsIndexConstant.FILE_NAME, searchSingleDTO.getKeyword()));

        if (searchSingleDTO.getIsFromText() != null && searchSingleDTO.getIsFromText()) {
            float textBoost = 1;
            //是否文本优先，如果是，该嵌套类型pageContents的boost值为2
            if(searchSingleDTO.getIsTextFirst()){
                textBoost = 2;
            }
            NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(
                    EsIndexConstant.PAGE_CONTENTS,
                            QueryBuilders.boolQuery()
                            .should(QueryBuilders.matchQuery(EsIndexConstant.PAGE_CONTENTS_CONTENT, searchSingleDTO.getKeyword()))
                    , ScoreMode.Avg)
                    .boost(textBoost)

                    .innerHit(new InnerHitBuilder()
                            .setSize(pageNum * pageSize)
                            .setHighlightBuilder(new HighlightBuilder()
                                    .field(EsIndexConstant.PAGE_CONTENTS_CONTENT)
                                    .preTags("<span style='color:red'>")
                                    .postTags("</span>")
                                    .requireFieldMatch(false)
                                     )
                            .setSorts(CollUtil.newArrayList(SortBuilders.scoreSort().order(SortOrder.DESC)))
                            .addFetchField(EsIndexConstant.PAGE_CONTENTS_CONTENT));
               boolQueryBuilder.should(nestedQueryBuilder);
        }
        if (searchSingleDTO.getIsFromImage() != null && searchSingleDTO.getIsFromImage()) {
            float picBoost = 1;
            //是否图片优先，如果是，该嵌套类型picContents的boost值为10
            if(searchSingleDTO.getIsImageFirst()){
                picBoost = 10;
            }
            NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(EsIndexConstant.PIC_CONTENTS, QueryBuilders.boolQuery()
                            .should(QueryBuilders.matchQuery(EsIndexConstant.PIC_CONTENTS_CONTENT, searchSingleDTO.getKeyword()))
                    , ScoreMode.Avg)
                    .boost(picBoost)
                    .innerHit(new InnerHitBuilder()
                            .setSize(pageNum * pageSize)
                            .setHighlightBuilder(new HighlightBuilder()
                                    .field(EsIndexConstant.PIC_CONTENTS_CONTENT)
                                    .preTags("<span style='color:red'>")
                                    .postTags("</span>")
                                    .requireFieldMatch(false)
                            )
                            .setSorts(CollUtil.newArrayList(SortBuilders.scoreSort().order(SortOrder.DESC)))) ;
            boolQueryBuilder.should(nestedQueryBuilder);
        }

        //boolQueryBuilder.should(boolQueryBuilder);
        searchSourceBuilder.query(boolQueryBuilder);

        //返回的字段中排出父级元素的嵌套类型的字段，因为内部命中查询已经返回了
        searchSourceBuilder.fetchSource(null, new String[]{EsIndexConstant.PAGE_CONTENTS,EsIndexConstant.PIC_CONTENTS}); //参数1：包含的字段，参数2：排除的字段


        searchRequest.source(searchSourceBuilder);
        SearchResponse response = esFileObjectMapper.search(searchRequest, RequestOptions.DEFAULT);

        SearchHit[] hits = response.getHits().getHits();
        //如果结果不为空
        if (hits.length == 0) {
            return AjaxJson.getSuccessData(null);
        }

        SearchSingleVO searchSingleVO = new SearchSingleVO();
        searchSingleVO.setMaxScore(response.getHits().getMaxScore()); //最大分数

        //我们这里只有一个结果，所以直接取第一个
        SearchHit hit = hits[0];
        EsFileObjectVO esFileObjectVO = new EsFileObjectVO();

        EsFileObject esFileObject = JSON.parseObject(hit.getSourceAsString(), EsFileObject.class);
        esFileObjectVO.setFromEsFileObject(esFileObject);


        Map<String, SearchHits> innerHits = hit.getInnerHits();
        //innerHits中的key是嵌套类型的字段名，最多只有两个，一个是pageContents，一个是picContents
        //如果是pageContents，那么就是文本查询，如果是picContents，那么就是图片查询
        if (innerHits.containsKey(EsIndexConstant.PAGE_CONTENTS)) {
            SearchHits pageContents = innerHits.get(EsIndexConstant.PAGE_CONTENTS);
            SearchHit[] pageContentsHits = pageContents.getHits();
            if(pageContentsHits.length > 0){
                List<EsPageObjectVO> esPageContentVOList = new ArrayList<>(pageContentsHits.length);
                for (SearchHit pageContentsHit : pageContentsHits) {
                    EsPageObjectVO esPageObjectVO = new EsPageObjectVO();
                    EsPageObject esPageObject = JSON.parseObject(pageContentsHit.getSourceAsString(), EsPageObject.class);
                    esPageObjectVO.setFromEsPageObject(esPageObject);
                    esPageObjectVO.setScore(pageContentsHit.getScore());
                    //获取高亮字段
                    Map<String, HighlightField> highlightFields = pageContentsHit.getHighlightFields();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
                        HighlightField highlightField = entry.getValue();
                        Text[] fragments = highlightField.getFragments();
                        for (Text fragment : fragments) {
                            stringBuilder.append(fragment.string());
                        }
                    }
                    esPageObjectVO.setHighlightContent(stringBuilder.toString());
                    esPageContentVOList.add(esPageObjectVO);
                }
                esFileObjectVO.setPageContents(esPageContentVOList);
            }
        }
        if (innerHits.containsKey(EsIndexConstant.PIC_CONTENTS)) {
            SearchHits picContents = innerHits.get(EsIndexConstant.PIC_CONTENTS);
            SearchHit[] picContentsHits = picContents.getHits();
            if(picContentsHits.length > 0){
                List<EsPicObjectVO> esPicContentVOList = new ArrayList<>(picContentsHits.length);
                for (SearchHit picContentsHit : picContentsHits) {
                    EsPicObjectVO esPicObjectVO = new EsPicObjectVO();
                    EsPicObject esPicObject = JSON.parseObject(picContentsHit.getSourceAsString(), EsPicObject.class);
                    esPicObjectVO.setFromEsPicObject(esPicObject);
                    esPicObjectVO.setScore(picContentsHit.getScore()); //分数
                    //获取高亮字段
                    Map<String, HighlightField> highlightFields = picContentsHit.getHighlightFields();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
                        HighlightField highlightField = entry.getValue();
                        Text[] fragments = highlightField.getFragments();
                        for (Text fragment : fragments) {
                            stringBuilder.append(fragment.string());
                        }
                    }
                    esPicObjectVO.setHighlightContent(stringBuilder.toString());
                    esPicContentVOList.add(esPicObjectVO);
                }
                esFileObjectVO.setPicContents(esPicContentVOList);
            }
        }


        searchSingleVO.setEsFileObjectVO(esFileObjectVO);

        return AjaxJson.getSuccessData(searchSingleVO);

    }

    @Override
    public int count() {
        //查找索引document_cn和document_en中的总数
        Long count = esFileObjectMapper.selectCount(new LambdaEsQueryWrapper().indexName(EsIndexConstant.CN_INDEX_NAME, EsIndexConstant.EN_INDEX_NAME));
        return count.intValue();
    }

    @Override
    public Boolean insert(EsFileObject fileObj) throws IOException {
        Boolean isEnglish = fileObj.getIsEnglish();
        String indexName = isEnglish ? EsIndexConstant.EN_INDEX_NAME : EsIndexConstant.CN_INDEX_NAME;
        //插入索引
        IndexRequest request = new IndexRequest(indexName);
        request.id(fileObj.getId());
        request.source(JSON.toJSONString(fileObj), XContentType.JSON);
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        return response.status().getStatus() == 201;
    }

    @Override
    public Boolean deleteById(String id) {
        //查找索引document_cn和document_en中是否存在该id然后删除
        Integer integer = esFileObjectMapper.deleteById(id, EsIndexConstant.CN_INDEX_NAME, EsIndexConstant.EN_INDEX_NAME);
        return integer > 0;
    }

    @Override
    public Boolean existsIndex(String indexName) {
        return esFileObjectMapper.existsIndex(indexName);
    }

    @Override
    public AjaxJson searchById(String id) {
        EsFileObject esFileObject = esFileObjectMapper.selectById(id, EsIndexConstant.CN_INDEX_NAME, EsIndexConstant.EN_INDEX_NAME);
        return AjaxJson.getSuccessData(esFileObject);
    }

    @Override
    public AjaxJson recommendHotDocument(){
        List<String> fileIds = redisService.getHotDocument();
        List<RecommendVO> recommendVOList = new ArrayList<>();
        if (fileIds != null) {
            fileIds.stream().forEach(fileId -> {
                EsFileObject esFileObject = esFileObjectMapper.selectById(fileId, EsIndexConstant.CN_INDEX_NAME, EsIndexConstant.EN_INDEX_NAME);
                RecommendVO recommendVO = new RecommendVO();
                recommendVO.setFileId(esFileObject.getId());
                recommendVO.setFileName(esFileObject.getFileName());
                recommendVO.setThumbnailId(esFileObject.getThumbId());
                recommendVO.setUploadUser(esFileObject.getUploadUser());
                recommendVO.setLike(esFileObject.getLike());
                recommendVO.setCollect(esFileObject.getCollect());
                recommendVO.setView(esFileObject.getView());
                recommendVO.setDownload(esFileObject.getDownload());
                recommendVOList.add(recommendVO);
            });
        }
        return AjaxJson.getSuccessData(recommendVOList);
    }
}
